// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#include <pollux/type/decimal_util.h>
#include <pollux/type/huge_int.h>

namespace kumo::pollux {
    namespace {
        std::string formatDecimal(uint8_t scale, int128_t unscaledValue) {
            POLLUX_DCHECK_GE(scale, 0);
            POLLUX_DCHECK_LT(
                static_cast<size_t>(scale), sizeof(DecimalUtil::kPowersOfTen));
            const bool isFraction = (scale > 0);
            if (unscaledValue == 0) {
                if (isFraction) {
                    return fmt::format("{:.{}f}", 0.0, scale);
                }
                return "0";
            }

            bool isNegative = (unscaledValue < 0);
            if (isNegative) {
                unscaledValue = ~unscaledValue + 1;
            }
            int128_t integralPart = unscaledValue / DecimalUtil::kPowersOfTen[scale];

            std::string fractionString;
            if (isFraction) {
                auto fraction =
                        std::to_string(unscaledValue % DecimalUtil::kPowersOfTen[scale]);
                fractionString += ".";
                // Append leading zeros.
                fractionString += std::string(
                    std::max(scale - static_cast<int>(fraction.size()), 0), '0');
                // Append the fraction part.
                fractionString += fraction;
            }

            return fmt::format(
                "{}{}{}", isNegative ? "-" : "", integralPart, fractionString);
        }
    } // namespace

    std::string DecimalUtil::toString(int128_t value, const TypePtr &type) {
        auto [precision, scale] = getDecimalPrecisionScale(*type);
        return formatDecimal(scale, value);
    }

    int32_t DecimalUtil::getByteArrayLength(int128_t value) {
        if (value < 0) {
            value = ~value;
        }
        int nbits;
        if (auto hi = HugeInt::upper(value)) {
            nbits = 128 - bits::countLeadingZeros(hi);
        } else if (auto lo = HugeInt::lower(value)) {
            nbits = 64 - bits::countLeadingZeros(lo);
        } else {
            nbits = 0;
        }
        return 1 + nbits / 8;
    }

    int32_t DecimalUtil::toByteArray(int128_t value, char *out) {
        int32_t length = getByteArrayLength(value);
        auto lowBig = melon::Endian::big<int64_t>(value);
        uint8_t *lowAddr = reinterpret_cast<uint8_t *>(&lowBig);
        if (length <= sizeof(int64_t)) {
            memcpy(out, lowAddr + sizeof(int64_t) - length, length);
        } else {
            auto highBig = melon::Endian::big<int64_t>(value >> 64);
            uint8_t *highAddr = reinterpret_cast<uint8_t *>(&highBig);
            memcpy(out, highAddr + sizeof(int128_t) - length, length - sizeof(int64_t));
            memcpy(out + length - sizeof(int64_t), lowAddr, sizeof(int64_t));
        }
        return length;
    }

    void DecimalUtil::computeAverage(
        int128_t &avg,
        int128_t sum,
        int64_t count,
        int64_t overflow) {
        if (overflow == 0) {
            divideWithRoundUp<int128_t, int128_t, int64_t>(
                avg, sum, count, false, 0, 0);
        } else {
            POLLUX_DCHECK_LE(overflow, count);
            __uint128_t quotMul, remMul;
            __int128_t quotSum, remSum;
            __int128_t remTotal;
            remMul = DecimalUtil::divideWithRoundUp<__uint128_t, __uint128_t, int64_t>(
                quotMul, kOverflowMultiplier, count, true, 0, 0);
            remMul *= overflow;
            quotMul *= overflow;
            remSum = DecimalUtil::divideWithRoundUp<__int128_t, __int128_t, int64_t>(
                quotSum, sum, count, true, 0, 0);
            DecimalUtil::divideWithRoundUp<__int128_t, __int128_t, int64_t>(
                remTotal, remMul + remSum, count, true, 0, 0);
            avg = quotMul + quotSum + remTotal;
        }
    }

    int32_t DecimalUtil::maxStringViewSize(int precision, int scale) {
        int32_t rowSize = precision + 1; // Number and symbol.
        if (scale > 0) {
            ++rowSize; // A dot.
        }
        if (precision == scale) {
            ++rowSize; // Leading zero.
        }
        return rowSize;
    }
} // namespace kumo::pollux
